/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.teststeps;

import com.eviware.soapui.config.GotoConditionConfig;
import com.eviware.soapui.config.GotoConditionTypeConfig;
import com.eviware.soapui.config.GotoStepConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.support.http.HttpRequestTestStep;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContainer;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.XPathReference;
import com.eviware.soapui.model.support.XPathReferenceContainer;
import com.eviware.soapui.model.support.XPathReferenceImpl;
import com.eviware.soapui.model.testsuite.SamplerTestStep;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.model.testsuite.TestStepResult.TestStepStatus;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlBoolean;
import org.apache.xmlbeans.XmlObject;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;

/**
 * TestStep that moves execution to another step based on the contents of a XML
 * Property
 *
 * @author ole.matzura
 */

public class WsdlGotoTestStep extends WsdlTestStepWithProperties implements XPathReferenceContainer,
        PropertyExpansionContainer {
    private GotoStepConfig gotoStepConfig;
    private List<GotoCondition> conditions = new ArrayList<GotoCondition>();
    private boolean canceled;

    private final static Logger log = Logger.getLogger(WsdlGotoTestStep.class);

    public WsdlGotoTestStep(WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest) {
        super(testCase, config, true, forLoadTest);

        if (!forLoadTest) {
            setIcon(UISupport.createImageIcon("/conditional_goto_step.png"));
        }
    }

    @Override
    public void afterLoad() {
        TestStepConfig config = getConfig();

        if (config.getConfig() == null) {
            gotoStepConfig = (GotoStepConfig) config.addNewConfig().changeType(GotoStepConfig.type);
        } else {
            gotoStepConfig = (GotoStepConfig) config.getConfig().changeType(GotoStepConfig.type);
            for (int c = 0; c < gotoStepConfig.sizeOfConditionArray(); c++) {
                conditions.add(new GotoCondition(gotoStepConfig.getConditionArray(c)));
            }
        }

        super.afterLoad();
    }

    public void resetConfigOnMove(TestStepConfig config) {
        super.resetConfigOnMove(config);

        gotoStepConfig = (GotoStepConfig) config.getConfig().changeType(GotoStepConfig.type);
        for (int c = 0; c < gotoStepConfig.sizeOfConditionArray(); c++) {
            conditions.get(c).setConfig(gotoStepConfig.getConditionArray(c));
        }
    }

    public TestStepResult run(TestCaseRunner runner, TestCaseRunContext context) {
        WsdlTestStepResult result = new WsdlTestStepResult(this);
        canceled = false;

        result.startTimer();

        SamplerTestStep previousStep = getTestCase().findPreviousStepOfType(this, SamplerTestStep.class);

        if (previousStep == null) {
            result.stopTimer();
            result.addMessage("Failed to find previous request step from [" + getName() + "]");
            result.setStatus(TestStepStatus.FAILED);
            return result;
        }

        GotoCondition target = runConditions(previousStep, context);
        if (target == null) {
            result.addMessage("Missing matching condition, moving on.");
        } else {
            String targetStepName = target.getTargetStep().trim();
            result.addMessage("Matched condition [" + targetStepName + "], transferring to [" + targetStepName + "]");
            runner.gotoStep(runner.getTestCase().getTestStepIndexByName(targetStepName));
        }

        result.stopTimer();
        result.setStatus(TestStepStatus.OK);
        return result;
    }

    public GotoCondition runConditions(SamplerTestStep previousStep, TestCaseRunContext context) {
        for (GotoCondition condition : conditions) {
            if (canceled) {
                break;
            }

            try {
                if (condition.evaluate(previousStep, context)) {
                    return condition;
                }
            } catch (Exception e) {
                log.error("Error making condition " + condition.getName() + "; " + e);
            }
        }

        return null;
    }

    public boolean cancel() {
        canceled = true;
        return canceled;
    }

    public int getConditionCount() {
        return conditions.size();
    }

    public GotoCondition getConditionAt(int index) {
        return conditions.get(index);
    }

    public GotoCondition addCondition(String name) {
        GotoCondition condition = new GotoCondition(gotoStepConfig.addNewCondition());
        condition.setName(name);
        condition.setType(GotoConditionTypeConfig.XPATH.toString());
        conditions.add(condition);
        return condition;
    }

    public void removeConditionAt(int index) {
        conditions.remove(index);
        gotoStepConfig.removeCondition(index);
    }

    public void release() {
        super.release();

        for (GotoCondition condition : conditions) {
            condition.release();
        }
    }

    public class GotoCondition implements PropertyChangeListener {
        public final static String TARGET_STEP_PROPERTY = "target_step";

        private GotoConditionConfig conditionConfig;
        private TestStep currentStep;
        private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

        public GotoCondition(GotoConditionConfig conditionConfig) {
            this.conditionConfig = conditionConfig;
            initListeners();
        }

        public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(listener);
        }

        public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
            propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
            propertyChangeSupport.removePropertyChangeListener(listener);
        }

        private void initListeners() {
            release();

            if (getTargetStep() != null) {
                int index = getTestCase().getTestStepIndexByName(getTargetStep());
                if (index != -1) {
                    currentStep = getTestCase().getTestStepAt(index);
                    currentStep.addPropertyChangeListener(TestStep.NAME_PROPERTY, this);
                }
            }
        }

        public void release() {
            if (currentStep != null) {
                currentStep.removePropertyChangeListener(this);
            }
        }

        public boolean evaluate(SamplerTestStep previousStep, TestCaseRunContext context) throws Exception {
            if (getExpression() == null || getExpression().trim().length() == 0) {
                throw new Exception("Missing expression in condition [" + getName() + "]");
            }

            if (getTargetStep() == null || getTargetStep().trim().length() == 0) {
                throw new Exception("Missing target step in condition [" + getName() + "]");
            }

            if (getType().equals(GotoConditionTypeConfig.XPATH.toString())) {
                TestRequest testRequest = previousStep.getTestRequest();
                // XmlObject xmlObject = XmlObject.Factory.parse(
                // testRequest.getResponse().getContentAsXml() );
                XmlObject xmlObject = XmlUtils.createXmlObject(testRequest.getResponse().getContentAsXml());

                String expression = PropertyExpander.expandProperties(context, getExpression());
                XmlObject[] selectPath = xmlObject.selectPath(expression);
                if (selectPath.length == 1 && selectPath[0] instanceof XmlBoolean) {
                    if (((XmlBoolean) selectPath[0]).getBooleanValue()) {
                        return true;
                    }
                }
            } else {
                log.error("Unkown condition type: " + getType());
            }

            return false;
        }

        protected void setConfig(GotoConditionConfig conditionConfig) {
            this.conditionConfig = conditionConfig;
        }

        public String getType() {
            return conditionConfig.getType();
        }

        public String getName() {
            return conditionConfig.getName();
        }

        public String getExpression() {
            return conditionConfig.getExpression();
        }

        public String getTargetStep() {
            return conditionConfig.getTargetStep();
        }

        public void setType(String type) {
            conditionConfig.setType(type);
        }

        public void setName(String name) {
            conditionConfig.setName(name);
        }

        public void setExpression(String expression) {
            conditionConfig.setExpression(expression);
        }

        public void setTargetStep(String targetStep) {
            String oldStep = getTargetStep();
            conditionConfig.setTargetStep(targetStep);
            initListeners();
            propertyChangeSupport.firePropertyChange(TARGET_STEP_PROPERTY, oldStep, targetStep);
        }

        public void propertyChange(PropertyChangeEvent evt) {
            conditionConfig.setTargetStep(evt.getNewValue().toString());
            propertyChangeSupport.firePropertyChange(TARGET_STEP_PROPERTY, evt.getOldValue(), evt.getNewValue());
        }

        public TestProperty getSourceProperty() {
            HttpRequestTestStep previousStep = (HttpRequestTestStep) getTestCase().findPreviousStepOfType(
                    WsdlGotoTestStep.this, HttpRequestTestStep.class);
            return previousStep == null ? null : previousStep.getProperty("Response");
        }
    }

    public boolean hasProperties() {
        return false;
    }

    public PropertyExpansion[] getPropertyExpansions() {
        List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();

        for (GotoCondition condition : conditions) {
            result.addAll(PropertyExpansionUtils.extractPropertyExpansions(this, condition, "expression"));
        }

        return result.toArray(new PropertyExpansion[result.size()]);
    }

    public XPathReference[] getXPathReferences() {
        List<XPathReference> result = new ArrayList<XPathReference>();

        for (GotoCondition condition : conditions) {
            if (StringUtils.hasContent(condition.getExpression())) {
                result.add(new XPathReferenceImpl("Condition for " + condition.getName() + " GotoCondition in "
                        + getName(), condition.getSourceProperty(), condition, "expression"));
            }
        }

        return result.toArray(new XPathReference[result.size()]);
    }
}
